package com.abo.yaas.view.widgets.tables;

import java.util.ArrayList;
import java.util.List;

import com.vaadin.data.Container;
import com.vaadin.data.Item;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.Table;

@SuppressWarnings("serial")
public class YaasPagedTable extends Table implements IPagedComponent {
    interface PageChangeListener {
        public void pageChanged(PagedTableChangeEvent event);
    }

    // first item shown in the view for the moment
    private int index = 0;

    private int internalPageLength = 20;

    private List<PageChangeListener> listeners = null;

    // Real container
    private Container.Indexed realContainer;

    private PageContainer shownContainer = new PageContainer();

    public YaasPagedTable() {
        this(null);
    }

    public YaasPagedTable(String caption) {
        super(caption);
        super.setPageLength(0);
        this.alwaysRecalculateColumnWidths = true;
        setInternalPageLength(15);
        addStyleName("pagedtable");
    }

    public void addListener(PageChangeListener listener) {
        if (listeners == null) {
            listeners = new ArrayList<PageChangeListener>();
        }
        listeners.add(listener);
    }

    private void addShownItem(Object itemId) {
        final Item realItem = realContainer.getItem(itemId);
        final Item shownItem = shownContainer.addItem(itemId);
        for (final Object property : realContainer.getContainerPropertyIds()) {
            shownItem.getItemProperty(property).setValue(
                    realItem.getItemProperty(property).getValue());
        }
    }

    public HorizontalLayout createControls() {
        final PagedTableControls controls = new PagedTableControls(this);
        addListener(controls);
        return controls;
    }

    private void fillVisibleContainer(int firstIndex) {
        shownContainer.removeAllItems();
        if (realContainer.size() != 0) {
            Object itemId = realContainer.getIdByIndex(firstIndex);
            addShownItem(itemId);
            for (int i = 1; i < internalPageLength; i++) {
                itemId = realContainer.nextItemId(itemId);
                if (itemId == null) {
                    break;
                }
                addShownItem(itemId);
            }
        }
        index = firstIndex;
        if (listeners != null) {
            final PagedTableChangeEvent event = new PagedTableChangeEvent(this);
            for (final PageChangeListener listener : listeners) {
                listener.pageChanged(event);
            }
        }
    }

    @Override
    public Container.Indexed getContainerDataSource() {
        return realContainer;
    }

    @Override
    public int getCurrentPage() {
        int page = (int) (Math.floor(index / internalPageLength) + 1);
        if (page < 1) {
            page = 1;
        }
        return page;
    }

    @Override
    public int getCurrentPageFirstItemIndex() {
        return 0;
    }

    public int getInternalPageLength() {
        return internalPageLength;
    }

    @Override
    public int getPageAmount() {
        final int size = realContainer.size();
        int pageCount = (int) Math.ceil(size / (internalPageLength * 1.0d));
        if (pageCount < 1) {
            pageCount = 1;
        }
        return pageCount;
    }

    @Override
    public void nextPage() {
        setPageFirstIndex(index + internalPageLength);
    }

    @Override
    public void previousPage() {
        setPageFirstIndex(index - internalPageLength);
    }

    @Override
    public void refreshCurrentPage() {
        setPageFirstIndex(index);
    }

    public void removeListener(PageChangeListener listener) {
        if (listeners == null) {
            listeners = new ArrayList<PageChangeListener>();
        }
        listeners.remove(listener);
    }

    public void setAlwaysRecalculateColumnWidths(
            boolean alwaysRecalculateColumnWidths) {
        this.alwaysRecalculateColumnWidths = alwaysRecalculateColumnWidths;
    }

    @Override
    public void setContainerDataSource(Container newDataSource) {
        if (!(newDataSource instanceof Container.Indexed)) {
            throw new IllegalArgumentException(
                    "PagedTable can only use containers that implement Container.Indexed");
        }

        realContainer = (Container.Indexed) newDataSource;
        shownContainer = new PageContainer();

        for (final Object object : realContainer.getContainerPropertyIds()) {
            shownContainer.addContainerProperty(object,
                    realContainer.getType(object), null);
        }

        fillVisibleContainer(0);
        super.setContainerDataSource(shownContainer);
    }

    @Override
    public void setCurrentPage(int page) {
        int newIndex = (page - 1) * internalPageLength;
        if (newIndex < 0) {
            newIndex = 0;
        }
        if (newIndex >= 0 && newIndex != index) {
            setPageFirstIndex(newIndex);
        }
    }

    public void setInternalPageLength(int internalPageLength) {
        this.internalPageLength = internalPageLength;
        setPageFirstIndex(index);
    }

    private void setPageFirstIndex(int firstIndex) {
        if (realContainer != null) {
            if (firstIndex <= 0) {
                firstIndex = 0;
            }
            if (firstIndex > realContainer.size() - 1) {
                final int size = realContainer.size() - 1;
                int pages = 0;
                if (internalPageLength != 0) {
                    pages = (int) Math.floor(0.0 + size / internalPageLength);
                }
                firstIndex = pages * internalPageLength;
            }
            shownContainer
                    .removeListener((Container.ItemSetChangeListener) this);
            fillVisibleContainer(firstIndex);
            shownContainer.addListener((Container.ItemSetChangeListener) this);
            containerItemSetChange(new Container.ItemSetChangeEvent() {
                @Override
                public Container getContainer() {
                    return shownContainer;
                }
            });
            if (alwaysRecalculateColumnWidths) {
                for (final Object columnId : shownContainer
                        .getContainerPropertyIds()) {
                    setColumnWidth(columnId, -1);
                }
            }
        }
    }

    @Override
    public void setPageLength(int pageLength) {
        throw new UnsupportedOperationException(
                "Page Length Should Be Set Via Internal Page Length Variable");
    }

    @Override
    public void sort(Object[] propertyId, boolean[] ascending)
            throws UnsupportedOperationException {
        if (realContainer instanceof Container.Sortable) {
            ((Container.Sortable) realContainer).sort(propertyId, ascending);
        } else if (realContainer != null) {
            throw new UnsupportedOperationException(
                    "Underlying Data does not allow sorting");
        }
        setPageFirstIndex(index);
    }
}
